Animations can be very powerful tools when used effectively, allowing you to see progression over time, iterate through plots, and help you tell a better story with the data. This guide will give an overview of packages, techniques, and tips for creating animated plots in R.

In this guide you will learn:

  • Necessary packages for creating animated plots
  • How to turn static plots into animations
  • Components of gganimate and what each function does
  • Ways to edit, improve, and save your animations

Getting Started

Packages

To create animations, there are some R packages you will need to install & load first. The main packages we will be using are:

  1. gganimate
  2. ggplot2
  3. tidyverse
  4. gapminder

You will likely need to install the following packages if you haven’t worked with animations before:

install.packages("gganimate")
install.packages("gapminder")

Then load the necessary packages:

library(gganimate)
library(ggplot2)
library(tidyverse)
library(gapminder)

Data

To create animated plots we must first have some tidy data to work with. We’ll start with an unique data set to get familiar with some of the basic features and then later explore some example data.

Head over to (Google Search Trends)[https://trends.google.com/trends/?geo=US] and search up a term that interests you. You should get a graph of the popularity of the search term over time. Click “add comparison” at the top and add at least two more terms (so you have 3 or more total).

One you have a graph of multiple terms, download this as a .csv file to your computer.

Note: Once you have your .csv downloaded, click on it in the bottom right “Files” pane and click “View File”. The .csv file will open in another tab. Delete the first two rows so that “Week” and the terms you searched are now at the top. Click “Save”, and now your .csv is ready for importing.

popularity <- read_csv("/Users/nolan/MACALESTER/Stat 456/multiTimeline.csv")
head(popularity)
## # A tibble: 6 × 4
##   Week       `World cup` `Premier League` `UEFA Champions League`
##   <date>           <dbl>            <dbl>                   <dbl>
## 1 2017-09-24           1                5                       7
## 2 2017-10-01           3                3                       1
## 3 2017-10-08           4                3                       1
## 4 2017-10-15           1                4                       7
## 5 2017-10-22           2                5                       1
## 6 2017-10-29           1                4                       8

We want this data in tidy format, so lets pivot_longer our data.

popularity <- popularity %>% 
  pivot_longer(cols = `World cup`:`UEFA Champions League`,
               names_to = "Search Term",
               values_to = "Popularity")

Now make a line plot the popularity over time:

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity") +
  theme_classic() +
  theme(legend.position = "top")

Now we’re ready for animations.

Creating Animations

Within gganimate, the main concept is the use of transition functions to create an animation. Other components of an animation the we will cover along with these functions are:

  • Labels
  • Views
  • Rendering
  • Shadows
  • Easing

We will walk through each of these in detail and build upon them to create some cool visualizations.

Basics

Transitions control how the data gets displayed in your animation. Do you want your data to populate over time by year, by category, or another filter? This is determined by the transition function you select. The list of common transition functions is shown below, along with their functionality.

Name Functionality
transition_reveal Reveal data along a given dimension
transition_filter Transition between different filters
transition_states Transition between several distinct stages of the data
transition_time Transition through distinct states in time
transition_layers Build up a plot, layer by layer

Transition functions are simply added to your regular ggplot statements following a “+”.

Transition functions also have a few common arguments that are useful to know as you often will want to adjust these. Typically, whatever variable you are animating on goes first. In addition, the other main arguments are:

Name Functionality
transition_length Controls the relative length of individual transitions
filter/layer/state_length Controls the relative length of pause at each state
range Controls the time range to animate (if applicable)
wrap Controls if the animation should wrap-around back to the start at the end

There are of course other arguments specific to each transition function, however these are the crucial ones to know when building animations.

Transition functions & Animation Components

Transition_reveal()

Lets try using transition_reveal() first. This transition will reveal the data along a given dimension – in this case time. This can be a good option when you have line charts or time-series data.

Add transition_reveal() to your plot, with the Week variable as the first argument.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)

You should now see your plot animated in the viewer in the bottom right panel of your screen!

Labels

Another thing we should make sure to add to our animation is how it is transitioning. While we know the animation is changing by week, our viewer may not realize or understand that. With some transition functions this may or may not be easily inferable, so it’s good practice to include this information on the plot.

We can add this information in the labs() part of our ggplot statement, specifying one of the corresponding transition variables related to the transition function we are using (see below).

Name Label variable(s)
transition_reveal frame_along
transition_filter previous_filter, closest_filter, next_filter
transition_states previous_state, closest_state, next_state
transition_time frame_time
transition_layers previous_layer, closest_layer, next_layer, nlayers

In this case, lets add title = “Week: {frame_along}” to our label.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)

Now we can see each week at the top as it is being displayed.

Views

Another thing we can alter about the animation is how the scales change. Currently they are static, which may not be the best when we have spread out data like this. You can change how the animation is viewed via view functions. The main one is:

  • view_follow()

You can also fix the x or y axis separately if it makes sense (it does in this case as we have values on a fixed scale from 0-100), using the fixed_x and fixed_y arguments.

Try adding view_follow(fixed_y = TRUE) to the previous animation as see how it looks.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week) +
  view_follow(fixed_y = TRUE)

This is a cool way to show data populating over time.

Rendering

We want to be able to save and display our animations in a good format for viewing. Saving animations can save space and time to load them, and adjusting features like duration can also greatly improve the final animation. There are a few functions that help with that.

To save animations as gifs you can use the anim_save() function, imputing the desired file name in the parentheses. Then, to load it use the knitr::include_graphics() function, with the file name in the parentheses. Try it out on a previous plot.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)

anim_save("popularity_anim")
knitr::include_graphics("popularity_anim", )

To adjust how animations are displayed, including duration, height/width, fps, and more, the animate() function is useful. Key arguments that can be changed are:

  • fps
  • duration
  • width
  • height
  • start_pause
  • end_pause
  • rewind

Save your plot and then try adjusting some of these to see how the output of the animation changes.

p <- popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)
animate(p, width = 700, height = 425, fps = 25, duration = 16, rewind = FALSE, start_pause = 25, end_pause = 25)

Transition_filter()

Now, perhaps we wanted to iterate through the different search terms, rather than seeing them all at once. In this case, we can use transition_filter(). This transition allows you to animate trough a range of filtering conditions – in this case Search Term.

Using the same plot as before, change transition_reveal() to transition_filter(). Then, add in the following arguments: transition_length, filter_length, and the filter conditions. These conditions should be the Search Term equal to each of your terms. Also, update the title label to the corresponding variable: “{closest_filter}”. See below for reference:

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "{closest_filter}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_filter(transition_length = 0.05,
                    filter_length = 2,
                    `Search Term` == "Premier League", `Search Term` == "UEFA Champions League", `Search Term` == "World cup")

When we run the plot we now get the animation transitioning through each of these filters, providing a different view of the same data.

Transition_states()

Another transition function is transition_states(). This function allows you to animate between several distinct stages of the data, whether that be before/during/after an event, by time segment, or another relevant partition.

Before we use this, lets focus our data down to a smaller section of time for one category. Filter your data to include only one term for the most recent 4-6 weeks of data, like so:

recent_term <- popularity %>% 
  filter(Week > "2022-08-01",
         `Search Term` == "Premier League")

Now, create a bar chart of this data, with Week on the x-axis and Popularity on the y-axis. Coloring by Popularity can also make this plot more visually appealing, but is not necessary.

recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic()

From here, we can add in the transition_states() function, as we have distinct stages (weeks) of data. Use the Week variable as the first argument in the transition function, and don’t forget to add a label for the transition! In this case you have a few options for the label, if you want to display the previous, current, on next state respectively.

recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic() +
  labs(title = "Week: {closest_state}") +
  transition_states(Week, wrap = FALSE)

Shadows

The previous animation you made was pretty neat, but it only really provided useful comparison for successive weeks, and not across the whole time span. Wouldn’t it be great if there was a way to keep the previous values displayed during the animation? Well, there is – with shadows!

Shadows allow you to show previous data during the course of an animation. The three shadow functions are:

  • shadow_mark()
  • shadow_trail()
  • shadow_wake()

Each offer different ways of representing past data in an animation, as their names suggest.

Try adding shadow_mark() to the previous bar chart animation and see what it does.

recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic() +
  labs(title = "Week: {closest_state}") +
  transition_states(Week, wrap = FALSE) +
  shadow_mark()

Now we can see the previous bars throughout the animation, which makes comparison across weeks a bit easier.

We will explore the other shadow functions later in other plots.

New Data

Some transition functions work better on different types of data. To showcase the other functions, we will switch to the gapminder data set. This is a great example data set for animations, and it contains data on worldwide economic, health, and other public information. Load the data set and preview it below:

head(gapminder)
## # A tibble: 6 × 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.
## 6 Afghanistan Asia       1977    38.4 14880372      786.

We will start again by creating a plot using some of the 6 variables in the data set. Here’s an example:

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  labs(x = "GDP per capita", y = "Life expectancy") +
  theme_classic()

Lets add a log transformation so we can see the data a bit better:

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy") +
  theme_classic()

Transiton_layers()

In some cases, you may want to show multiple geoms on the same plot, like a line and bar chart together. Transition_layers() allows for this functionality, and adds these layers in one-by-one during the anitmation.

First, lets edit the previous plot and add some more geom elements to it. Remove the coloring and size, and add a histogram of the GDP Per Capita and a geom_smooth of the points.

gapminder %>% 
  ggplot() +
  geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
  geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
  geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
  theme_classic()

Now, lets add transition_layer() to animate this plot.

gapminder %>% 
  ggplot() +
  geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
  geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
  geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", alpha = "GDP") +
  theme_classic() +
  transition_layers()

This can be a great way to show multiple types of plots layered together as seen here.

Transition_time()

As the name implies, this function allows you to transition through distinct states in time, like year. This is a better option for point data, whereas transition_reveal() is better for line data.

To see it in action, add transition_time() to the original gapminder plot, with year as the argument, and adding the correct label for this function.

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year)

Try adding another shadow function to this animation:

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year) +
  shadow_wake(wake_length = 0.2)

This really highlights the general trend of increasing life expectancy & GDP per-capita over time.

Easing

Suppose you wanted to change the rate of the transition in the animation. You could go about doing that via easing. Easing controls the “acceleration” per-se of the animation. By default it is linear/constant speed during transitions as we have seen, but you could make it slow down or speed up during the transitions too. This is done via the ease_aes() function.

To have slow-to-fast transitions we can use the argument: quadratic-in and to have fast-to-slow transitions down use the argument: quadratic-out.

Try adding one of these to the previous animation.

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year) +
  shadow_wake(wake_length = 0.2) +
  ease_aes('quadratic-out')

Conslusion

Creating animations in R is relatively easy and quite fun to do. They are also very practical in that they can showcase different elements and dimensions of data that a static plot simply cannot. From this tutorial, you now have the skills necessary to create, edit, and save animations like the one below.

One final task: Edit your last visualization to include each of the 5 animation components we covered:

  • Labels
  • Views
  • Rendering
  • Shadows
  • Easing
final_plot <- gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  facet_wrap(~continent) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year) +
  shadow_wake(wake_length = 0.2) +
  ease_aes('linear') +
  view_follow()

animate(final_plot, fps = 20, duration = 10, rewind = FALSE, start_pause = 20, end_pause = 20)